<template>
  <div>
    <v-dialog v-model="choosePPTDialog" width="1200px" persistent>
      <v-card>
        <v-card-title class="text-h5">
          选择演示文档
          <v-spacer></v-spacer>

          <v-text-field
            v-model="searchForm.title"
            append-icon="mdi-magnify"
            label="请输入文档标题关键词进行搜索"
            single-line
            hide-details
            @click:append="searchPPT"
            @keydown.enter="searchPPT"
          ></v-text-field>
          <v-btn
            depressed
            color="warning"
            small
            class="mt-5 ml-5"
            @click="resetSearchPPT"
            >重置</v-btn
          >
        </v-card-title>
        <v-sheet class="px-6 pt-2">
          <v-data-table
            height="530px"
            item-key="id"
            v-model="pptSelected"
            :headers="headers"
            :items="pptList"
            show-select
            :single-select="true"
            fixed-header
            show-expand
            :single-expand="true"
            :server-items-length="total"
            :footer-props="{
              'items-per-page-text': '选择每页数量',
              'items-per-page-options': [10, 20]
            }"
            :loading="pptTableLoading"
            :options.sync="pageOptions"
            @pagination="changeTablePage"
          >
            <template v-slot:item.update_time="{ item }">
              {{ realTime(item.update_time) }}
            </template>
            <template v-slot:item.tag="{ item }">
              <v-chip
                small
                outlined
                label
                v-for="(tag, index) in JSON.parse(item.tag)"
                :key="index"
                class="ma-1"
                color="primary"
                >{{ tag }}</v-chip
              >
            </template>
            <template v-slot:expanded-item="{ headers, item }">
              <td :colspan="headers.length" style="box-shadown: none">
                文档介绍：{{ item.intro }}
              </td>
            </template>
            <template
              v-slot:footer.page-text="{ itemsLength, pageStart, pageStop }"
            >
              <span style="margin-right: 20px">总数：{{ itemsLength }}</span
              ><span>范围：{{ pageStart }}-{{ pageStop }}</span>
            </template>
          </v-data-table>
        </v-sheet>
        <v-card-actions>
          <v-spacer></v-spacer>
          <v-btn color="primary" text @click="choosePPTDialog = false">
            取消
          </v-btn>
          <v-btn color="primary" text @click="changePPT"> 确定 </v-btn>
        </v-card-actions>
      </v-card>
    </v-dialog>
    <v-dialog v-model="enterTipDialog" width="350" persistent>
      <v-card>
        <v-card-title class="text-h5"> 提示 </v-card-title>
        <v-card-text class="text-subtitle-1"
          >即将进入会议，请保持会议秩序！</v-card-text
        >
        <v-card-actions>
          <v-spacer></v-spacer>
          <v-btn color="primary" text @click="enterTipDialog = false">
            确定
          </v-btn>
        </v-card-actions>
      </v-card>
    </v-dialog>
    <v-dialog v-model="kickUserDialog" width="350" persistent>
      <v-card>
        <v-card-title class="text-h5"> 提示 </v-card-title>
        <v-card-text class="text-subtitle-1"
          ><span style="color: #e53935"
            >是否踢出{{ tempMaster.realname }}？</span
          ></v-card-text
        >
        <v-card-actions>
          <v-spacer></v-spacer>
          <v-btn color="primary" text @click="kickUserDialog = false">
            取消
          </v-btn>
          <v-btn color="primary" text @click="kickUser"> 确定 </v-btn>
        </v-card-actions>
      </v-card>
    </v-dialog>
    <v-dialog v-model="closeRoomDialog" width="350" persistent>
      <v-card>
        <v-card-title class="text-h5"> 提示 </v-card-title>
        <v-card-text class="text-subtitle-1"
          ><span style="color: #e53935">是否关闭此会议？</span></v-card-text
        >
        <v-card-actions>
          <v-spacer></v-spacer>
          <v-btn color="primary" text @click="closeRoomDialog = false">
            取消
          </v-btn>
          <v-btn color="primary" text @click="closeRoom"> 确定 </v-btn>
        </v-card-actions>
      </v-card>
    </v-dialog>
    <v-dialog v-model="quitRoomDialog" width="350" persistent>
      <v-card>
        <v-card-title class="text-h5"> 提示 </v-card-title>
        <v-card-text class="text-subtitle-1">是否退出此会议？</v-card-text>
        <v-card-actions>
          <v-spacer></v-spacer>
          <v-btn color="primary" text @click="quitRoomDialog = false">
            取消
          </v-btn>
          <v-btn color="primary" text @click="quitRoom"> 确定 </v-btn>
        </v-card-actions>
      </v-card>
    </v-dialog>
    <v-navigation-drawer
      app
      class="elevation-3"
      disable-resize-watcher
      v-model="drawerLeft"
      width="280"
      stateless
    >
      <v-list-item>
        <v-list-item-content>
          <v-list-item-title style="font-size: 26px"
            >ROOM {{ roomInfo.roomId }}
            <v-btn icon class="mb-1 ml-n1" @click="copyText(roomInfo.roomId)"
              ><v-icon>content_copy</v-icon></v-btn
            ></v-list-item-title
          >
          <v-list-item-subtitle class="text-subtitle-1" v-if="roomInfo.owner">
            <v-badge
              :color="
                roomInfo.owner && roomInfo.member[roomInfo.owner.id]
                  ? 'green'
                  : 'grey lighten-2'
              "
              dot
              offset-y="10"
              offset-x="-5"
            >
              <span>
                主持人：
                {{ roomInfo.owner.realname }} /
                {{ roomInfo.owner.nickname }}
              </span>
            </v-badge>
          </v-list-item-subtitle>
          <v-list-item-subtitle class="text-subtitle-1" v-if="roomInfo.master">
            <v-badge
              :color="
                roomInfo.master && roomInfo.member[roomInfo.master.id]
                  ? 'green'
                  : 'grey lighten-2'
              "
              dot
              offset-y="10"
              offset-x="-5"
            >
              <span>
                放映者：
                {{ roomInfo.master.realname }} /
                {{ roomInfo.master.nickname }}
              </span>
            </v-badge>
          </v-list-item-subtitle>
          <v-list-item-subtitle class="text-subtitle-1">
            <span>总人数： {{ memberArray(roomInfo.member).length }}人</span>
          </v-list-item-subtitle>
          <v-list-item-subtitle class="text-subtitle-1" v-if="roomInfo.ppt">
            <span>演示文档： {{ roomInfo.ppt.title || '请选择文档' }}</span>
          </v-list-item-subtitle>
          <v-list-item-subtitle class="text-subtitle-1" v-if="roomInfo.ppt">
            <span>开始： {{ realTime(roomInfo.beginTime) }}</span>
          </v-list-item-subtitle>
          <v-list-item-subtitle class="text-subtitle-1" v-if="roomInfo.ppt">
            <span
              >结束：
              {{
                realTime(roomInfo.beginTime + roomInfo.roomTime * 1000)
              }}</span
            >
          </v-list-item-subtitle>
        </v-list-item-content>
      </v-list-item>

      <v-divider></v-divider>

      <v-list>
        <v-subheader>在线用户</v-subheader>
        <v-list-item-group @change="userMenuChange" v-model="whichUser">
          <v-list-item
            v-for="(item, index) in memberArray(roomInfo.member)"
            :key="index"
            @contextmenu="showUserMenu($event, item)"
          >
            <v-list-item-content class="pt-0">
              <v-list-item-title>
                <v-badge
                  inline
                  color="primary"
                  content="主持人"
                  v-if="roomInfo.owner && roomInfo.owner.id === item.id"
                >
                  <span
                    >{{ item.realname }}{{ index === 0 ? '(自己)' : '' }}</span
                  >
                </v-badge>
                <v-badge
                  inline
                  color="warning"
                  content="放映者"
                  v-else-if="roomInfo.master && roomInfo.master.id === item.id"
                >
                  <span
                    >{{ item.realname }}{{ index === 0 ? '(自己)' : '' }}</span
                  >
                </v-badge>
                <v-badge offset-y="10" offset-x="-5" color="success" dot v-else>
                  <span
                    >{{ item.realname }}{{ index === 0 ? '(自己)' : '' }}</span
                  >
                </v-badge>
              </v-list-item-title>
              <v-list-item-subtitle
                v-text="realTime(item.joinTime)"
              ></v-list-item-subtitle>
            </v-list-item-content>
            <v-list-item-action>
              <v-btn
                icon
                @click="openVolume(item)"
                color="primary"
                v-if="$store.getters.userinfo.id === item.id"
              >
                <v-icon>{{
                  item.volumeUp ? 'volume_up' : 'volume_off'
                }}</v-icon>
              </v-btn>
              <v-btn icon color="error" v-else-if="item.volumeUp">
                <v-icon>volume_up</v-icon>
              </v-btn>
            </v-list-item-action>
          </v-list-item>
        </v-list-item-group>
      </v-list>

      <template v-slot:append>
        <div class="pa-2 d-flex">
          <v-btn
            class="text-subtitle-1 mr-2"
            depressed
            style="flex-grow: 1"
            outlined
            @click="closeRoomDialog = true"
            v-if="
              roomInfo.owner && $store.getters.userinfo.id === roomInfo.owner.id
            "
            color="error"
          >
            关闭会议
          </v-btn>
          <v-btn
            class="text-subtitle-1"
            depressed
            outlined
            style="flex-grow: 1"
            color="primary"
            @click="quitRoomDialog = true"
          >
            退出会议
          </v-btn>
        </div>
      </template>
    </v-navigation-drawer>

    <v-main>
      <div class="canv-main">
        <div class="canv-wrap" id="canvWrap" ref="canvWrap">
          <div style="position: relative" class="elevation-3">
            <v-sheet
              class="oper"
              v-show="
                isDraw &&
                roomInfo.master &&
                roomInfo.master.id === $store.getters.userinfo.id
              "
            >
              <v-btn-toggle
                dense
                class="rounded-0"
                @change="changeBtn"
                v-model="whichBtn"
              >
                <v-btn @click="undoLine">
                  <v-icon>reply</v-icon>
                </v-btn>
                <v-btn @click="changeColor">
                  <v-icon :color="penColor">whatshot</v-icon>
                </v-btn>
                <v-btn @click="changeWeight">
                  <v-icon>line_weight</v-icon>{{ penWidth }}
                </v-btn>
              </v-btn-toggle>
            </v-sheet>
            <v-sheet class="oper" v-show="!isDraw">
              <v-btn-toggle
                dense
                class="rounded-0"
                @change="changeBtn"
                v-model="whichBtn"
              >
                <v-btn
                  v-if="
                    roomInfo.master &&
                    roomInfo.master.id === $store.getters.userinfo.id
                  "
                  @click="prevPage"
                >
                  <v-icon>chevron_left</v-icon>
                </v-btn>

                <v-btn
                  v-if="
                    roomInfo.master &&
                    roomInfo.master.id === $store.getters.userinfo.id
                  "
                  @click="nextPage"
                >
                  <v-icon>chevron_right</v-icon>
                </v-btn>

                <v-btn>{{ nowPage }}</v-btn>
              </v-btn-toggle>
            </v-sheet>
            <v-scale-transition origin="left bottom 0">
              <v-sheet class="chat-expend-left" v-show="!chatFrameShow">
                <v-badge
                  :color="msgCount === 0 ? 'primary' : 'error'"
                  :dot="msgCount === 0 ? true : false"
                  :content="msgCount"
                  overlap
                  bordered
                >
                  <v-btn-toggle
                    dense
                    class="rounded-0"
                    @change="changeBtnChat"
                    v-model="whichBtnChat"
                  >
                    <v-btn @click="openChatFrame">
                      <v-icon>compare_arrows</v-icon>
                    </v-btn>
                  </v-btn-toggle>
                </v-badge>
              </v-sheet>
            </v-scale-transition>
            <!-- 公共聊天 -->
            <v-scale-transition origin="left bottom 0">
              <div class="chat-out" v-show="chatFrameShow">
                <v-sheet class="chat-expend">
                  <v-btn-toggle
                    dense
                    class="rounded-0"
                    @change="changeBtnChat"
                    v-model="whichBtnChat"
                  >
                    <v-btn @click="closeChatFrame">
                      <v-icon>compare_arrows</v-icon>
                    </v-btn>
                  </v-btn-toggle>
                </v-sheet>
                <div class="chat-frame">
                  <div class="chat-content">
                    <div
                      v-for="(item, index) in roomInfo.chatMsg"
                      :key="index"
                      class="mb-2"
                    >
                      <div
                        class="msg-wrap"
                        style="display: flex; flex-direction: column"
                        :style="{
                          'align-items':
                            item.userId === $store.getters.userinfo.id
                              ? 'flex-end'
                              : 'flex-start'
                        }"
                      >
                        <div class="msg-who">
                          <span
                            v-if="item.userId === $store.getters.userinfo.id"
                            >{{ realTime(item.time, true) }}</span
                          >
                          {{ item.realname }}
                          <span
                            v-if="item.userId !== $store.getters.userinfo.id"
                            >{{ realTime(item.time, true) }}</span
                          >
                        </div>
                        <div class="msg-item">{{ item.msg }}</div>
                      </div>
                    </div>
                  </div>
                  <div class="chat-send">
                    <v-text-field
                      v-model="chatMsg"
                      label="请输入聊天信息"
                      placeholder="请输入聊天信息"
                      solo
                      dense
                      class="elevation-0 rounded-0"
                      hide-details
                      append-icon="outbox"
                      @click:append="sendMsg"
                      @keydown.enter="sendMsg"
                    ></v-text-field>
                  </div>
                </div>
              </div>
            </v-scale-transition>
            <div v-show="!isDraw"><canvas id="canv" ref="canv"></canvas></div>
            <div v-show="isDraw">
              <canvas id="drawCanv" ref="drawCanv"></canvas>
            </div>
          </div>
        </div>
      </div>
    </v-main>

    <div
      v-show="false"
      id="audioWrap"
      style="position: fixed; right: 0; top: 200px; width: 300px"
    >
      <!-- <audio id="localAudio" controls></audio> -->
    </div>
    <v-menu
      v-model="userMenuShow"
      :position-x="userMenuX"
      :position-y="userMenuY"
      absolute
      offset-y
      transition="false"
    >
      <v-list class="py-0 canv-menu">
        <v-list-item-group
          v-model="userOper"
          color="primary"
          @change="chooseUserOper"
        >
          <v-list-item>
            <v-list-item-content>
              <v-list-item-title>用户信息</v-list-item-title>
            </v-list-item-content>
          </v-list-item>
          <v-list-item
            @click="changeMaster"
            v-if="
              roomInfo.owner && roomInfo.owner.id === $store.getters.userinfo.id
            "
          >
            <v-list-item-content>
              <v-list-item-title>指定放映</v-list-item-title>
            </v-list-item-content>
          </v-list-item>
          <v-list-item
            v-if="
              roomInfo.owner && roomInfo.owner.id === $store.getters.userinfo.id
            "
            @click="banVolume"
          >
            <v-list-item-content>
              <v-list-item-title>禁止发言</v-list-item-title>
            </v-list-item-content>
          </v-list-item>
          <v-list-item
            v-if="
              roomInfo.owner && roomInfo.owner.id === $store.getters.userinfo.id
            "
            @click="kickUserDialog = true"
          >
            <v-list-item-content>
              <v-list-item-title>踢出会议</v-list-item-title>
            </v-list-item-content>
          </v-list-item>
        </v-list-item-group>
      </v-list>
    </v-menu>

    <v-menu :open-on-hover="!fixBtn" offset-y v-model="menuShow">
      <template v-slot:activator="{ on, attrs }">
        <div
          class="oper-btn"
          @mousedown="handleFixBtn"
          :style="{ left: fixBtnX + 'px', top: fixBtnY + 'px' }"
          v-bind="attrs"
          v-on="on"
        >
          <v-icon dark> mdi-format-list-bulleted-square </v-icon>
        </div>
      </template>

      <v-list>
        <v-list-item-group
          v-model="menuOper"
          color="primary"
          @change="chooseOper"
        >
          <v-list-item
            @click="prevPage"
            v-if="
              roomInfo.master &&
              $store.getters.userinfo.id === roomInfo.master.id
            "
          >
            <v-list-item-content>
              <v-list-item-title>上一页</v-list-item-title>
            </v-list-item-content>
          </v-list-item>
          <v-list-item
            @click="nextPage"
            v-if="
              roomInfo.master &&
              $store.getters.userinfo.id === roomInfo.master.id
            "
          >
            <v-list-item-content>
              <v-list-item-title>下一页</v-list-item-title>
            </v-list-item-content>
          </v-list-item>

          <v-list-item @click="changeCanv">
            <v-list-item-content>
              <v-list-item-title>开闭画板</v-list-item-title>
            </v-list-item-content>
          </v-list-item>

          <v-list-item @click="exchangeChatFrame">
            <v-list-item-content>
              <v-list-item-title>开闭聊天框</v-list-item-title>
            </v-list-item-content>
          </v-list-item>

          <v-list-item @click="openSidebar">
            <v-list-item-content>
              <v-list-item-title>侧边栏开闭</v-list-item-title>
            </v-list-item-content>
          </v-list-item>
          <v-list-item @click="screenOpen">
            <v-list-item-content>
              <v-list-item-title>全屏播放开闭</v-list-item-title>
            </v-list-item-content>
          </v-list-item>
          <v-list-item
            v-if="
              roomInfo.master &&
              $store.getters.userinfo.id === roomInfo.master.id
            "
            @click="choosePPTDialog = true"
          >
            <v-list-item-content>
              <v-list-item-title>切换演示文档</v-list-item-title>
            </v-list-item-content>
          </v-list-item>
          <v-list-item
            v-if="
              roomInfo.master &&
              $store.getters.userinfo.id === roomInfo.owner.id
            "
            @click="banAllVolume"
          >
            <v-list-item-content>
              <v-list-item-title>全员静音</v-list-item-title>
            </v-list-item-content>
          </v-list-item>
        </v-list-item-group>
      </v-list>
    </v-menu>
  </div>
</template>

<script>
import { io } from 'socket.io-client'
import $ from 'jquery'
import { fabric } from 'fabric'
import { formatTime } from '@/utils/index.js'
import { getPPT } from '@/api/ppt'
import { $ws, iceConf } from '@/../backend.js'
export default {
  name: 'room',
  data() {
    return {
      penColor: 'black',
      penWidth: 1,
      isDraw: false,
      msgCount: 0,
      prevChatMsgCount: 0,
      // 这个dialog为了解决audio在用户未点击的状况下不能自动播放
      enterTipDialog: true,
      constraints: {
        video: false,
        audio: {
          noiseSuppression: true, //降噪
          echoCancellation: true // 回音消除
        }
      },
      monPeer: {},
      sendPeer: null,
      volumeIcon: 'volume_off',
      chatMsg: '',
      chatFrameShow: false,
      isIntercept: true,
      tempMaster: {},
      kickUserDialog: false,
      userMenuShow: false,
      userMenuX: 0,
      userMenuY: 0,
      userOper: null,
      whichUser: null,
      whichBtn: null,
      whichBtnChat: null,
      pptTableLoading: false,
      total: 0,
      pageOptions: {
        page: 1,
        itemsPerPage: 10
      },
      pptSelected: [],
      choosePPTDialog: false,
      searchForm: {
        title: ''
      },
      headers: [
        {
          text: '标题',
          value: 'title',
          sortable: false
        },
        {
          text: '标签',
          value: 'tag',
          sortable: false
        },
        {
          text: '更新时间',
          value: 'update_time',
          sortable: false,
          width: '180px'
        },
        { text: '', value: 'data-table-expand' }
      ],
      pptList: [],
      closeRoomDialog: false,
      quitRoomDialog: false,
      menuShow: false,
      menuOper: null,
      drawerLeft: true,
      fixBtn: false,
      fixBtnX: $(window).width() - 100,
      fixBtnY: 40,
      roomInfo: {},
      canv: null,
      scale: 16 / 9, // 画布宽高比例
      zoom: 1, // 画布当前缩放
      cw: 1920, // 画布实际宽
      ch: 1080, // 画布实际高
      ow: 1920, // 画布比例宽
      oh: 1080, // 画布比例高
      gx: 20, // 画布的x边距
      gy: 20 // 画布的y边距
    }
  },
  computed: {
    nowPage() {
      return `${this.roomInfo.pptPage + 1}/${
        this.roomInfo.ppt?.content?.length
      }`
    },
    realTime() {
      return (time, isHour = false) => formatTime(time, isHour)
    },
    memberArray() {
      return (member) => {
        if (!member) {
          return []
        }
        const arr = []
        for (const x in member) {
          if (parseInt(x) !== parseInt(this.$store.getters.userinfo.id)) {
            arr.push(member[x])
          }
        }
        arr.sort((a, b) => a.id - b.id)
        arr.unshift(member[this.$store.getters.userinfo.id])
        return arr
      }
    }
  },
  // 离开本页面之前
  beforeRouteLeave(to, from, next) {
    if (this.isIntercept) {
      if (!window.confirm('是否退出会议？')) {
        return next(false) // 必须加上false，要不然route中的路由信息会发生变化
      }
      this.transfer.disconnect()
      next()
    } else {
      next()
    }
  },
  destroyed() {
    if (this.sendStream) {
      this.sendStream.getTracks().forEach((t) => {
        t.stop()
      })
    }
    // 关闭弹窗
    if (this.webFrame) {
      this.webFrame.close()
    }
    // 取消事件监听
    this.transfer.disconnect()
  },

  mounted() {
    this.makeMedia()
    this.getPPT()
    this.$nextTick(() => {
      this.initCanv()
    })
    this.mountEvent()
    this.transfer = io($ws, {
      reconnectionDelayMax: 10000,
      query: {
        roomId: this.$route.params.id
      },
      extraHeaders: { Authorization: `${localStorage.getItem('token')}` }
    })
    this.transfer.on('connect', () => {
      console.log('连接成功')
    })
    this.transfer.on('disconnect', () => {
      console.log('断开连接')
    })
    this.transfer.on('throw err', (e) => {
      this.$toast.error(e.msg)
      if (e.code === 11000 || e.code === 12000) {
        setTimeout(() => {
          this.$router.go(-1)
        }, 800)
      }
    })

    // 接受线条撤销消息
    this.transfer.on('receive undo line', () => {
      this.roomInfo.drawCanv.objects.pop()
      this.reMakeDrawCanv()
    })

    // 接受画的线条
    this.transfer.on('receive draw line', (msg) => {
      this.roomInfo.drawCanv.objects.push(msg)
      this.reMakeDrawCanv()
    })

    // 成功禁止单个用户发言
    this.transfer.on('receive ban volume', () => {
      // 关闭发言
      this.sendStream.getTracks().forEach((t) => {
        t.enabled = false
      })
      this.transfer.emit('sendpeer volume off')
    })

    // 接到发言人取消发言的通知
    this.transfer.on('receive sendpeer volume off', (e) => {
      this.roomInfo.member = e.member
      this.roomInfo.volumePeer = e.volumePeer
    })

    // 自己接到发言人发言的通知
    this.transfer.on('self receive sendpeer volume up', (e) => {
      this.roomInfo.member = e.member
      this.roomInfo.volumePeer = e.volumePeer
      this.sendStream.getTracks().forEach((t) => {
        t.enabled = true
      })
    })

    // 接收到发言人发言的通知
    this.transfer.on('receive sendpeer volume up', (e) => {
      this.roomInfo.member = e.member
      this.roomInfo.volumePeer = e.volumePeer
      this.establishConnection()
    })

    // 监听信令服务器发来的offer，answer等
    this.transfer.on('peer msg', (msg) => {
      if (msg.type === 'offer') {
        if (
          msg.sendId in this.monPeer ||
          parseInt(msg.sendId) === parseInt(this.$store.getters.userinfo.id)
        ) {
          return
        }

        // 初始化通道
        this.monPeer[msg.sendId] = this.makePeer()
        this.monPeer[msg.sendId].addStream(this.sendStream)

        // 监听发言人添加音频流的事件
        this.monPeer[msg.sendId].onaddstream = ((x) => {
          return (e) => {
            if ($(`#remoteAudio${x}`).length === 0) {
              $('#audioWrap').append(
                `<audio id="remoteAudio${x}" controls autoplay></audio>`
              )
            }
            const remoteAudio = $(`#remoteAudio${x}`)[0]
            if ('srcObject' in remoteAudio) {
              remoteAudio.srcObject = e.stream
            } else {
              remoteAudio.src = URL.createObjectURL(e.stream)
            }
            remoteAudio.play()
          }
        })(msg.sendId)

        // 设置远端描述
        this.monPeer[msg.sendId].setRemoteDescription(
          new RTCSessionDescription(msg.offer)
        )

        // 应答
        this.monPeer[msg.sendId]
          .createAnswer()
          .then((answer) => {
            // 设置本地描述
            this.monPeer[msg.sendId].setLocalDescription(answer)
            this.transfer.emit('peer msg', {
              type: 'answer',
              answer: answer,
              sendId: this.$store.getters.userinfo.id,
              sendSocketId:
                this.roomInfo.member[this.$store.getters.userinfo.id].socketId,
              toId: msg.sendId,
              toSocketId: msg.sendSocketId
            })
          })
          .catch((err) => {
            console.log(err)
          })
      } else if (msg.type === 'answer') {
        this.monPeer[msg.sendId].setRemoteDescription(
          new RTCSessionDescription(msg.answer)
        )
      } else if (msg.type === 'candidate') {
        this.monPeer[msg.sendId].addIceCandidate(
          new RTCIceCandidate(msg.candidate)
        )
      }
    })

    // 接收聊天信息
    this.transfer.on('receive chat msg', (e) => {
      this.roomInfo.chatMsg = e
      this.msgCount = this.roomInfo.chatMsg.length - this.prevChatMsgCount
      this.$nextTick(() => {
        $('.chat-content').scrollTop($('.chat-content')[0].scrollHeight)
      })
    })

    this.transfer.on('receive self kick', () => {
      this.isIntercept = false
      this.$toast.info('您已被踢出该会议')
      setTimeout(() => {
        this.$router.go(-1)
      }, 800)
    })
    this.transfer.on('receive kick user', (e) => {
      this.$toast.success(`已踢出${e.user.realname}`)
      this.roomInfo.member = e.member
    })
    this.transfer.on('receive master', (e) => {
      this.$toast.success('已切换放映者为' + e.realname)
      this.roomInfo.master = e
      this.canDraw()
    })
    this.transfer.on('receive close room', () => {
      this.$toast.warn('会议已结束，即将退出')
      this.isIntercept = false
      setTimeout(() => {
        this.$router.go(-1)
      }, 800)
    })
    this.transfer.on('receive page', (e) => {
      this.changePage(e.pptPage)
    })
    this.transfer.on('receive ppt', (e) => {
      for (const x in e) {
        e[x] = JSON.parse(e[x])
      }
      this.roomInfo.ppt = e.ppt
      this.roomInfo.pptId = e.pptId
      this.roomInfo.pptPage = e.pptPage
      this.reMakePPT()
      this.$toast.success('切换文档成功')
    })
    this.transfer.on('self join room', (e) => {
      for (const x in e) {
        e[x] = JSON.parse(e[x])
      }
      this.roomInfo = e
      this.reMakePPT()
      this.reMakeDrawCanv()
      this.canDraw()

      // 获取上一次打开之前的消息数量
      this.prevChatMsgCount = this.roomInfo.chatMsg.length

      // console.log('房间信息', this.roomInfo)
      // console.log(this.monPeer)

      // 初始化媒体能力
      navigator.mediaDevices
        .getUserMedia(this.constraints)
        .then((stream) => {
          this.sendStream = stream
          this.sendStream.getTracks().forEach((t) => {
            t.enabled = false
          })
          this.establishConnection()
        })
        .catch((err) => {
          console.log(err)
          this.$toast.error('请刷新页面并打开麦克风权限')
        })
    })
    this.transfer.on('other join room', (e) => {
      this.roomInfo.member = e
    })
    this.transfer.on('other leave room', (e) => {
      this.roomInfo.member = e.member
      this.roomInfo.volumePeer = e.volumePeer
      delete this.monPeer[e.userId]
      $(`#remoteAudio${e.userId}`).remove()
    })
    this.transfer.on('unauthorized', (error, callback) => {
      if (
        error.data.type == 'UnauthorizedError' ||
        error.data.code == 'invalid_token'
      ) {
        // redirect user to login page perhaps or execute callback:
        callback()
        console.log('User token has expired')
      }
    })
  },
  methods: {
    canDraw() {
      if (this.roomInfo.master.id === this.$store.getters.userinfo.id) {
        this.drawCanv.isDrawingMode = true
      } else {
        this.drawCanv.isDrawingMode = false
      }
    },
    changeCanv() {
      if (!this.isDraw) {
        this.isDraw = true
        this.$toast.info('已开启画板')
      } else {
        this.isDraw = false
        this.$toast.info('已开启演示')
      }
    },
    undoLine() {
      if (this.roomInfo.master.id !== this.$store.getters.userinfo.id) {
        return
      }
      this.transfer.emit('undo line')
    },
    changeColor() {
      if (this.penColor === 'black') {
        this.penColor = 'red'
      } else if (this.penColor === 'red') {
        this.penColor = 'blue'
      } else if (this.penColor === 'blue') {
        this.penColor = 'black'
      } else {
        this.penColor = 'black'
      }
      this.drawCanv.freeDrawingBrush.color = this.penColor
    },
    changeWeight() {
      this.penWidth += 4
      if (this.penWidth > 13) {
        this.penWidth = 1
      }
      this.drawCanv.freeDrawingBrush.width = this.penWidth
    },
    exchangeChatFrame() {
      this.chatFrameShow = !this.chatFrameShow
      if (this.chatFrameShow === false) {
        this.prevChatMsgCount = this.roomInfo.chatMsg.length
        this.msgCount = 0
      }
    },
    openVolume(item) {
      if (!this.sendStream) {
        this.$toast.error('请刷新页面，打开麦克风权限')
        return
      }
      if (item.volumeUp) {
        // 关闭发言
        this.sendStream.getTracks().forEach((t) => {
          t.enabled = false
        })
        this.transfer.emit('sendpeer volume off')
      } else {
        // 打开发言
        this.transfer.emit('sendpeer volume up')
      }
    },
    // 非发言者主动发送offer
    establishConnection() {
      // 与发言人进行连接
      for (const x in this.roomInfo.volumePeer) {
        if (
          x in this.monPeer ||
          parseInt(x) === parseInt(this.$store.getters.userinfo.id)
        ) {
          continue
        }
        this.monPeer[x] = this.makePeer()
        this.monPeer[x].addStream(this.sendStream)

        // 监听发言人添加音频流的事件
        this.monPeer[x].onaddstream = (e) => {
          if ($(`#remoteAudio${x}`).length === 0) {
            $('#audioWrap').append(
              `<audio id="remoteAudio${x}" controls autoplay></audio>`
            )
          }
          const remoteAudio = $(`#remoteAudio${x}`)[0]
          if ('srcObject' in remoteAudio) {
            remoteAudio.srcObject = e.stream
          } else {
            remoteAudio.src = URL.createObjectURL(e.stream)
          }
          remoteAudio.play()
        }

        // 监听本人获取到ice候选人信息的时候
        this.monPeer[x].onicecandidate = ((x) => {
          return (e) => {
            if (e.candidate) {
              this.transfer.emit('peer msg', {
                type: 'candidate',
                candidate: e.candidate,
                sendId: this.$store.getters.userinfo.id,
                sendSocketId:
                  this.roomInfo.member[this.$store.getters.userinfo.id]
                    .socketId,
                toId: x,
                toSocketId: this.roomInfo.member[x].socketId
              })
            }
          }
        })(x)

        // 主动发送给发言人
        this.monPeer[x]
          .createOffer()
          .then((offer) => {
            // 设置本地描述
            this.monPeer[x].setLocalDescription(offer)
            // 通过信令服务器发给发言人
            this.transfer.emit('peer msg', {
              type: 'offer',
              offer: offer,
              sendId: this.$store.getters.userinfo.id,
              sendSocketId:
                this.roomInfo.member[this.$store.getters.userinfo.id].socketId,
              toId: x,
              toSocketId: this.roomInfo.member[x].socketId
            })
          })
          .catch((err) => {
            console.log(err)
          })
      }
    },
    // polyfill
    makeMedia() {
      // 老的浏览器可能根本没有实现 mediaDevices，所以我们可以先设置一个空的对象
      if (navigator.mediaDevices === undefined) {
        navigator.mediaDevices = {}
      }
      // 一些浏览器部分支持 mediaDevices。我们不能直接给对象设置 getUserMedia
      // 因为这样可能会覆盖已有的属性。这里我们只会在没有getUserMedia属性的时候添加它。
      if (navigator.mediaDevices.getUserMedia === undefined) {
        navigator.mediaDevices.getUserMedia = function (constraints) {
          // 首先，如果有getUserMedia的话，就获得它
          const getUserMedia =
            navigator.webkitGetUserMedia || navigator.mozGetUserMedia

          // 一些浏览器根本没实现它 - 那么就返回一个error到promise的reject来保持一个统一的接口
          if (!getUserMedia) {
            return Promise.reject(
              new Error('getUserMedia is not implemented in this browser')
            )
          }
          // 否则，为老的navigator.getUserMedia方法包裹一个Promise
          return new Promise(function (resolve, reject) {
            getUserMedia.call(navigator, constraints, resolve, reject)
          })
        }
      }
    },
    // 生成RTCPeerConnection对象
    makePeer() {
      const PeerConnection =
        window.RTCPeerConnection ||
        window.mozRTCPeerConnection ||
        window.webkitRTCPeerConnection
      return new PeerConnection(iceConf)
    },
    copyText(text) {
      const textarea = document.createElement('textarea')
      $(textarea).css({ position: 'fixed' })
      $(textarea).attr('readonly', 'readonly') //设置只读属性防止手机上弹出软键盘
      textarea.value = text
      document.body.appendChild(textarea)
      textarea.select()
      document.execCommand('copy')
      document.body.removeChild(textarea)
    },
    openChatFrame() {
      this.chatFrameShow = true
      this.$nextTick(() => {
        $('.chat-content').scrollTop($('.chat-content')[0].scrollHeight)
      })
    },
    closeChatFrame() {
      this.chatFrameShow = false
      this.prevChatMsgCount = this.roomInfo.chatMsg.length
      this.msgCount = 0
    },
    sendMsg() {
      if (this.chatMsg === '') {
        return
      }
      this.transfer.emit('send msg', this.chatMsg)
      this.chatMsg = ''
    },
    kickUser() {
      this.kickUserDialog = false
      this.transfer.emit('kick user', this.tempMaster)
    },
    banVolume() {
      if (this.roomInfo.owner.id !== this.$store.getters.userinfo.id) {
        this.$toast.warn('您没有权限禁止成员发言')
        return
      }
      if (!this.roomInfo.member[this.tempMaster.id].volumeUp) {
        this.$toast.warn('此成员并未发言')
        return
      }
      this.transfer.emit('ban volume', this.tempMaster)
    },
    banAllVolume() {
      if (this.roomInfo.owner.id !== this.$store.getters.userinfo.id) {
        this.$toast.warn('您没有权限全员静音')
        return
      }
      this.transfer.emit('ban all volume')
    },
    changeMaster() {
      if (
        !this.roomInfo.owner ||
        this.roomInfo.owner.id !== this.$store.getters.userinfo.id
      ) {
        return
      }
      // 切换放映者
      this.transfer.emit('change master', this.tempMaster)
    },
    showUserMenu(e, item) {
      e.preventDefault()
      this.userMenuShow = false
      this.userMenuX = e.clientX
      this.userMenuY = e.clientY
      this.tempMaster = item
      this.$nextTick(() => {
        this.userMenuShow = true
      })
    },
    chooseUserOper() {
      this.$nextTick(() => {
        this.userOper = null
      })
    },
    userMenuChange() {
      this.$nextTick(() => {
        this.whichUser = null
      })
    },
    changeBtn() {
      this.$nextTick(() => {
        this.whichBtn = null
      })
    },
    changeBtnChat() {
      this.$nextTick(() => {
        this.whichBtnChat = null
      })
    },
    changeTablePage() {
      this.getPPT()
    },
    getPPT() {
      this.pptTableLoading = true
      getPPT({
        title: this.searchForm.title,
        pageNum: this.pageOptions.page,
        pageSize: this.pageOptions.itemsPerPage
      })
        .then((res) => {
          this.pptList = res.data.data.pptList
          this.total = res.data.data.total
        })
        .catch(() => {})
        .complete(() => {
          setTimeout(() => {
            this.pptTableLoading = false
          }, 1000)
        })
    },
    quitRoom() {
      this.quitRoomDialog = false
      setTimeout(() => {
        this.$router.go(-1)
      }, 300)
    },
    closeRoom() {
      this.closeRoomDialog = false
      setTimeout(() => {
        this.transfer.emit('close room')
      }, 300)
    },
    nextPage() {
      if (this.$store.getters.userinfo.id !== this.roomInfo.master.id) {
        return
      }
      if (this.roomInfo.pptPage <= this.roomInfo.ppt.content.length - 2) {
        if (this.lockPageOper) {
          this.$toast.warn('操作太过频繁')
          return
        }
        this.roomInfo.pptPage++
        this.renderCanv()
        this.transfer.emit('change page', { pptPage: this.roomInfo.pptPage })
        this.lockPageOper = setTimeout(() => {
          this.lockPageOper = null
        }, 200)
      }
    },
    prevPage() {
      if (this.$store.getters.userinfo.id !== this.roomInfo.master.id) {
        return
      }
      if (this.roomInfo.pptPage >= 1) {
        if (this.lockPageOper) {
          this.$toast.warn('操作太过频繁')
          return
        }
        this.roomInfo.pptPage--
        this.renderCanv()
        this.transfer.emit('change page', { pptPage: this.roomInfo.pptPage })
        this.lockPageOper = setTimeout(() => {
          this.lockPageOper = null
        }, 200)
      }
    },
    changePage(page) {
      this.roomInfo.pptPage = page
      this.renderCanv()
    },
    screenOpen() {
      if ($(window).height() === window.screen.height) {
        this.drawerLeft = true
        this.gx = 20
        this.gy = 20
        this.exitScreen()
      } else {
        this.drawerLeft = false
        this.gx = 0
        this.gy = 0
        this.fullScreen()
      }
    },
    //全屏
    fullScreen() {
      const el = document.documentElement
      const rfs =
        el.requestFullScreen ||
        el.webkitRequestFullScreen ||
        el.mozRequestFullScreen ||
        el.msRequestFullScreen

      if (rfs) {
        rfs.call(el)
      }
    },
    //退出全屏
    exitScreen() {
      const el = document
      const cfs =
        el.cancelFullScreen ||
        el.webkitCancelFullScreen ||
        el.mozCancelFullScreen ||
        el.exitFullScreen

      if (cfs) {
        cfs.call(el)
      }
    },
    openWebFrame(item) {
      return () => {
        if (this.webFrame && !this.webFrame.closed) {
          this.webFrame.focus()
          return
        }
        const width = 1400
        const height = 780
        const left = (window.outerWidth - width) / 2
        const top = (window.outerHeight - height) / 2
        this.webFrame = window.open(
          item.webUrl,
          'webFrame',
          `left=${left}, top=${top}, width=${width}, height=${height}`
        )
        this.webFrame.focus()
      }
    },
    openSidebar() {
      if (this.drawerLeft) {
        this.gx = 0
        this.gy = 0
        this.drawerLeft = false
      } else {
        this.gx = 20
        this.gy = 20
        this.drawerLeft = true
      }
    },
    chooseOper() {
      setTimeout(() => {
        this.menuOper = null
      })
    },
    reMakePPT() {
      if (!this.roomInfo.ppt) {
        return
      }
      this.roomInfo.ppt.content = JSON.parse(this.roomInfo.ppt.content || '[]')
      this.roomInfo.ppt.content.forEach((item) => {
        item.objects.forEach((obj) => {
          obj.selectable = false
          if (obj.webUrl) {
            obj.hoverCursor = 'pointer'
          } else {
            obj.hoverCursor = 'default'
          }
        })
      })
      this.renderCanv()
    },
    reMakeDrawCanv() {
      if (!this.roomInfo.drawCanv) {
        return
      }
      this.renderDrawCanv()
    },
    renderDrawCanv() {
      this.drawCanv.loadFromJSON(this.roomInfo.drawCanv)
    },
    renderCanv() {
      this.canv.loadFromJSON(
        this.roomInfo.ppt.content[this.roomInfo.pptPage || 0],
        () => {
          this.canv.getObjects().forEach((item) => {
            if (item.webUrl) {
              item.on('mousedown', this.openWebFrame(item))
            }
          })
        }
      )
    },
    searchPPT() {
      this.pageOptions.page = 1
      this.getPPT()
    },
    resetSearchPPT() {
      this.searchForm.title = ''
      this.pageOptions.page = 1
      this.getPPT()
    },
    changePPT() {
      if (!this.pptSelected || this.pptSelected.length === 0) {
        this.$toast.warn('请选择您要演示的文档')
        return
      }
      if (this.roomInfo.master.id !== this.$store.getters.userinfo.id) {
        return
      }
      this.transfer.emit('change ppt', { pptId: this.pptSelected[0].id })
      this.choosePPTDialog = false
    },
    handleFixBtn(e) {
      this.fixBtn = true
      this.beginX = e.pageX - this.fixBtnX
      this.beginY = e.pageY - this.fixBtnY
    },
    initCanv() {
      // 初始化临时画布
      this.canv = new fabric.Canvas('canv', {
        backgroundColor: 'white',
        width: this.ow,
        height: this.oh,
        selection: false
        // skipTargetFind: true,
      })
      // 初始化画板
      this.drawCanv = new fabric.Canvas('drawCanv', {
        backgroundColor: 'white',
        width: this.ow,
        height: this.oh,
        selection: false,
        skipTargetFind: true,
        isDrawingMode: false
      })
      this.drawCanv.on('path:created', (e) => {
        if (this.roomInfo.master.id !== this.$store.getters.userinfo.id) {
          return
        }
        this.transfer.emit('draw line', e.path)
      })
      this.getSize()
      this.resetSize()
    },
    // 页面大小改变时获取画布应变为的大小
    getSize() {
      const tw = $('#canvWrap').width()
      const th = $('#canvWrap').height()
      const scale = tw / th
      // 非常长
      if (scale >= this.scale) {
        this.ch = th - this.gy * 2
        this.cw = this.ch * this.scale
      } else {
        this.cw = tw - this.gx * 2
        this.ch = this.cw / this.scale
      }
      this.zoom = this.cw / this.ow
    },
    // 重置画布大小
    resetSize() {
      this.canv.setZoom(this.zoom)
      this.canv.setWidth(this.cw)
      this.canv.setHeight(this.ch)
      this.drawCanv.setZoom(this.zoom)
      this.drawCanv.setWidth(this.cw)
      this.drawCanv.setHeight(this.ch)
    },
    mountEvent() {
      $(window).on('beforeunload', () => {
        if (!/.*\/room\/.*/.test(window.location.href)) {
          return
        }
        if (this.sendStream) {
          this.sendStream.getTracks().forEach((t) => {
            t.stop()
          })
        }
        if (this.webFrame) {
          this.webFrame.close()
        }
      })
      $(window).on('resize', () => {
        if (!/.*\/room\/.*/.test(window.location.href)) {
          return
        }
        // // 获取全屏状态
        // if ($(window).height() === window.screen.height) {
        //   this.expendIcon = 'zoom_in_map'
        // } else {
        //   this.expendIcon = 'zoom_out_map'
        // }
        setTimeout(() => {
          this.getSize()
          this.resetSize()
          this.fixBtnX = $(window).width() - 100
          this.fixBtnY = 40
        }, 300)
      })
      $(document).on('mouseup', () => {
        if (!/.*\/room\/.*/.test(window.location.href)) {
          return
        }
        this.fixBtn = false
      })
      $(document).on('contextmenu', (e) => {
        if (!/.*\/room\/.*/.test(window.location.href)) {
          return
        }
        e.preventDefault()
      })
      $(document).on('keydown', (e) => {
        if (!/.*\/room\/.*/.test(window.location.href)) {
          return
        }
        // 回车和右键
        if (e.keyCode === 39) {
          this.nextPage()
        }
        // 左键
        if (e.keyCode === 37) {
          this.prevPage()
        }
      })
      $(document).on('mousemove', (e) => {
        if (!/.*\/room\/.*/.test(window.location.href)) {
          return
        }
        if (this.fixBtn) {
          this.menuShow = false
          this.fixBtnX = e.pageX - this.beginX
          this.fixBtnY = e.pageY - this.beginY
          if (this.fixBtnX + 60 > $(window).width()) {
            this.fixBtnX = $(window).width() - 60
          }
          if (this.fixBtnX < 0) {
            this.fixBtnX = 0
          }
          if (this.fixBtnY + 60 > $(window).height()) {
            this.fixBtnY = $(window).height() - 60
          }
          if (this.fixBtnY < 0) {
            this.fixBtnY = 0
          }
        }
      })
    }
  }
}
</script>

<style scoped>
.canv-main {
  overflow: hidden;
  width: 100%;
  height: 100vh;
}
.canv-wrap {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100%;
  height: 100%;
  background: rgb(226, 226, 226);
}
.oper-btn {
  position: fixed;
  display: flex;
  justify-content: center;
  align-items: center;
  width: 60px;
  height: 60px;
  opacity: 0.6;
  background: #1565c0;
  box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.432);
  border-radius: 3px;
  user-select: none;
  z-index: 10;
}
.oper-btn:hover {
  opacity: 1;
  cursor: default;
}

::v-deep
  .v-data-table
  > .v-data-table__wrapper
  tbody
  tr.v-data-table__expanded__content {
  box-shadow: none;
  background: rgb(240, 240, 240);
}

.oper {
  position: absolute;
  right: 0;
  bottom: 0;
  z-index: 10;
  opacity: 0.6;
  transition: opacity 0.2s;
}
.oper:hover {
  opacity: 1;
}

.chat-out {
  position: absolute;
  left: 0;
  bottom: 0;
  z-index: 10;
}
.chat-frame {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 280px;
  height: 340px;
  border-radius: 0 20px 0 0;
  overflow: hidden;
}
.chat-content {
  flex-shrink: 1;
  padding: 20px 10px 60px 10px;
  height: 100%;
  overflow-y: scroll;
  background: rgb(231, 231, 231);
}

::-webkit-scrollbar {
  width: 5px;
}
::-webkit-scrollbar-track {
  background-color: rgb(243, 243, 243);
}
::-webkit-scrollbar-thumb {
  background: rgb(114, 114, 114);
}

.chat-send {
  position: absolute;
  bottom: 0;
  width: calc(100% - 5px);
}

.chat-send
  ::v-deep
  .v-text-field.v-text-field--solo:not(.v-text-field--solo-flat)
  > .v-input__control
  > .v-input__slot {
  box-shadow: none !important;
  background: rgba(255, 255, 255, 1);
}

.chat-expend {
  position: absolute;
  right: -48px;
  bottom: 0;
  z-index: 10;
  opacity: 0.6;
  transition: opacity 0.2s;
}

.chat-expend:hover {
  opacity: 1;
}

.chat-expend-left {
  position: absolute;
  left: 0;
  bottom: 0;
  z-index: 10;
  opacity: 0.6;
  transition: opacity 0.2s;
}

.chat-expend-left:hover {
  opacity: 1;
}

.msg-who {
  color: grey;
  font-size: 14px;
}

.msg-item {
  max-width: 200px;
  padding: 6px 8px;
  border-radius: 8px;
  background: white;
  word-break: break-all;
  word-wrap: break-word;
}
</style>
